<template>
  <view class="root">
    <uni-im-list v-if="visibleMsgList.length" class="uni-im-list" :scrollTop="scrollTop" :scroll-into-view="scrollIntoView" :paddingBottom="paddingBottom" ref="uni-im-list" @scroll="onScroll" @scrolltolower="onScrollToLower">
     <template v-for="(msg,index) in visibleMsgList" :key="msg.unique_id || msg._id">
		 <uni-im-list-item :ref="'item-'+index">
		   <view v-if="index === 0" class="data-state-tip-box" @appear="beforeLoadMore">
       <uni-im-load-state v-if="canAnchor || !isMpPlatform" :status="hasMore?'loading':'noMore'" 
          :contentText='{"contentrefresh": "正在加载历史消息","contentnomore": "没有更多历史消息"}'></uni-im-load-state>
			 <template v-else>
			   <button v-if="hasMore" class="loadMore-btn" size="mini" @click="beforeLoadMore" :loading="loadMoreIng">{{loadMoreIng ? '加载中':'点击加载更多'}}</button>
			   <text v-else class="data-state-tip-text">没有更多历史消息</text>
			 </template>
		   </view>
		 
		   <view :class="['item',msg.type]" :id="'item-'+index" @click="clickItem">
			 <label class="msg-box" :class="{'active-msg':msg._id === activeMsgId || msg.unique_id === activeMsgId}" @click="checkMsg(msg)">
			   <template v-if="chooseMore">
				 <checkbox :checked="checkedMsgList.find(i=>i._id == msg._id) != undefined" class="checkbox" />
				 <view class="mask"></view>
			   </template>
			   <!-- <text style="width: 750rpx;text-align: center;border: 1px solid #000;">{{'item-'+index}}</text> -->
			   <uni-im-msg :msg="msg" :id="msg._id" :self="current_uid() == msg.from_uid" :index="index" @appear="msgOnAppear(msg._id)"
				 @putChatInputContent="putChatInputContent" :equalPrevTime="equalPrevTime(index)" 
				 :avatar_file="conversation.avatar_file" @showMsgById="showMsgById" @showControl="showControl" 
				 @loadMore="loadMore" @chatInputContentAddcallUser="chatInputContentAddcallUser" @retriesSendMsg="retriesSendMsg" 
				 @viewMsg="viewMsg" :ref="'uni-im-msg'" class="uni-im-msg"
			   >
			   </uni-im-msg>
			 </label>
		   </view>
		 </uni-im-list-item>
     </template>
      <template v-slot:floating-block>
        <view v-if="hasNewMsg" class="new-msg-bar" @click="showLast">
          <uni-icons type="pulldown" size="18" color="#007fff"></uni-icons>
          <text>有新消息</text>
        </view>
      </template>
    </uni-im-list>
    <uni-im-load-state v-else :status="hasMore?'loading':'noMore'" class="mg-15" :contentText='{"contentrefresh": "加载中","contentnomore": "- 没有聊天记录 -"}'></uni-im-load-state>

    <!-- <view class="slider-box">
			val:{{val}} scrollTop:{{scrollTop}}
			<slider value="1" @change="sliderChange" min="1" max="14" step="1" />
		</view> -->
    <!-- <view style="position: fixed;top: 100px;width: 500rpx;">
			paddingBottom：{{paddingBottom}}
			scrollTop：{{scrollTop}}
			visibleMsgList.length:{{visibleMsgList.length}}
			scrollIntoView:{{scrollIntoView}}
			<button @click="showLast">showLast</button>
		</view> -->

    <view v-if="call_list.length" class="showCallMe" @click="showCallMe">@回复我({{call_list.length}})</view>

    <uni-popup @change="closeGroupNotification" ref="group-notification-popup" type="center" class="group-notification-popup">
      <uni-im-group-notification ref="group-notification"></uni-im-group-notification>
    </uni-popup>

    <!-- #ifdef H5 -->
    <uni-im-view-msg ref="view-msg"></uni-im-view-msg>
    <!-- #endif -->
  </view>
</template>

<script>
/**
 * uni-im-msg-list 组件，渲染一个会话列表。
 * 
 * 内部使用 {@link module:uni-im-list} 组件实现列表功能，使用 {@link module:uni-im-msg} 组件实现每条消息的渲染。
 * 
 * @module
 * @see module:chat
 * @see module:uni-im-list
 * @see module:uni-im-list-item
 */
  import uniIm from '@/uni_modules/uni-im/sdk/index.js';
  import {
    store as uniIdStore
  } from '@/uni_modules/uni-id-pages/common/store';

  import uniImList from './components/uni-im-list/uni-im-list';
  import uniImListItem from './components/uni-im-list-item/uni-im-list-item';

  // 一页多少条数据
  let pageLimit = 30
  // 当前页面滚动条高度
  let currentScrollTop = 0

  // 跟踪卷滚条的位置，以判定当前卷滚条是否为贴底状态
  class ScrollTracker {
    position = 0
    at_bottom = false
    scroll(position) {
      // scrolltolower 事件触发后仍会有 scroll 事件，
      // 所以只要位置在递增就仍然是贴底状态，
      // 如果逆向变化则脱离贴底状态。
      if (this.position > position) {
        this.at_bottom = false
      }
      this.position = position
    }
    reachBottom() {
      this.at_bottom = true
    }
    isAtBottom() {
      return this.at_bottom
    }
  }
  
  let intersectionObserver = null;
  let appearObj = {};

  export default {
    components: {
      uniImList,
      uniImListItem
    },
    emits:['checkedMsgList','chatInputContentAddcallUser','showControl','clickItem','retriesSendMsg','putChatInputContent'],
    computed: {
      ...uniIm.mapState(['systemInfo', 'isWidescreen']),
      loadState() {
        return this.hasMore ? '正在加载历史消息' : '没有更多历史消息'
      },
      hasMore() {
        return this.conversation.hasMore
      },
      visibleMsgList() {
        const msgList = this.conversation.msgList || []
        // 过滤掉，撤回消息的指令型消息 、 群头像更新（指令型）消息
        const visibleMsgList = uniIm.utils.filterMsgList(msgList)
        // 返回倒数 laterRenderIndex 条消息，实现懒渲染
                                    .slice(-this.laterRenderIndex)
        this.$nextTick(() => {
          uniIm.utils.throttle(this.setIntersectionObserver, 1000);
        })
        return visibleMsgList
      },
      canAnchor() {
        // #ifdef APP-NVUE
        return true
        // #endif
        
        // #ifndef APP-NVUE
        const {
          browserName,
          model
        } = this.systemInfo
        return browserName != "safari" && !model.includes("iPhone")
        // #endif
      },
      isMpPlatform() {
        // #ifdef MP
        return true
        // #endif
        // #ifndef MP
        return false
        // #endif
      }
    },
    data() {
      return {
        val: 0,
        conversation: {},
        scrollIntoView: "",
        scrollTop: 0,
        scrollTracker: new ScrollTracker(),
        hasNewMsg: false,
        call_list: [],
        activeMsgId: "",
        loadMoreIng: false,
		// 延迟渲染，避免页面卡顿
        laterRenderIndex: pageLimit
      }
    },
    watch: {
      'conversation.call_list'(call_list) {
        this.call_list = call_list
      },
      'conversation.unreadGroupNotification': {
        handler(unreadGroupNotification) {
          // 弹出群公告
          if (unreadGroupNotification && unreadGroupNotification.content) {
            // 判断列表中是否已经渲染了此群公告，是则 call 当前用户。否则弹框提示
            let groupNotificationMsg = [...this.visibleMsgList].reverse().find(msg => msg.action ===
              'update-group-info-notification')
            console.log('groupNotificationMsg', groupNotificationMsg);
            if (groupNotificationMsg) {
              this.conversation.call_list.push(groupNotificationMsg._id)
            } else {
              this.$refs["group-notification-popup"].open()
              this.$nextTick(() => {
                this.$refs["group-notification"].notification = unreadGroupNotification
              })
            }
          }
        },
        immediate: true
      }
    },
    props: {
      paddingBottom: {
        default: ''
      },
      conversationId: {
        default () {
          return false
        }
      },
      chooseMore: {
        default: false
      },
      checkedMsgList: {
        default () {
          return []
        }
      }
    },
    async mounted() {
      // for (var i = 0; i < 10; i++) {
      // 	this.msgList.unshift({
      // 		t:i
      // 	})
      // }
    },
    destroyed() {
      // console.log('destroyed')
      if (intersectionObserver){
        intersectionObserver.disconnect()
      }
    },
    methods: {
      async init() {
        if (intersectionObserver){
          intersectionObserver.disconnect()
        }
        this.conversation = await uniIm.conversation.get(this.conversationId)
        // init data --start
        this.scrollIntoView = ''
        this.scrollTop = 0
        this.laterRenderIndex = pageLimit
        currentScrollTop = 0

        if (!this.conversation.isInit) {
          this.conversation.hasMore = true
          await this.loadMore()
          this.conversation.isInit = true
          if (this.conversation.hasMore && this.visibleMsgList.length < pageLimit) {
            console.log('不满一屏时，再loadMore一次');
            await this.loadMore()
          }
        }

        // #ifndef APP
        // app端会自动固定在底部，其他端需要执行一次显示最后一条
        this.$nextTick(() => {
          this.showLast(300)
          // TODO 延迟300毫秒 临时解决，未知情况下$nextTick后仍然不能滚到最后一条的问题
          setTimeout(() => {
            this.showLast(300)
          }, 300);
        })
        // #endif
      },
      async loadMore(callback) {
        let datas = []
        if(this.laterRenderIndex < this.conversation.msgList.length){
          let oldVisibleMsgList = JSON.parse(JSON.stringify(this.visibleMsgList))
          await this.canHoldScrollDo(()=>{
            this.laterRenderIndex += pageLimit
          })
          await this.$nextTick()
          // 新加载到的数据为：oldVisibleMsgList和this.visibleMsgList的差集
          datas = this.visibleMsgList.filter(i => !oldVisibleMsgList.some(j => j._id == i._id))
          // console.log('beforeLoadMore 懒渲染=>',this.laterRenderIndex,this.conversation.msgList.length);
        }else{
          datas = await this.conversation.msgManager.getHistory()
          // console.error(`加载到${datas.length}条，历史聊天数据`);
          // console.log('加载到历史聊天数据：', datas);
          if (datas.length === 0) {
            this.conversation.hasMore = false
          } else {
            await this.insertMsg(datas)
          }
        }
        if (typeof callback === 'function') {
          // 为兼容 web pc端特殊场景 不能使用$nextTick
          setTimeout(() => {
            callback(datas)
          }, 0)
        }
        return datas
      },
      async beforeLoadMore() {
        if (!this.loadMoreIng && this.hasMore && currentScrollTop < 30) {
          this.loadMoreIng = true
          await this.loadMore()
          this.loadMoreIng = false
        }
      },
      msgOnAppear(msgId){
        uniIm.utils.throttle(()=>{
          let index = this.visibleMsgList.findIndex(i => i._id == msgId)
          if (index == -1) {
            return //因为是异步的，可能已经被销毁了替换了新对象
          }
        }, 1000);
      },
      async setIntersectionObserver() {
        // console.log('setIntersectionObserver');
        if (intersectionObserver) {
          // console.log('intersectionObserver存在','执行销毁');
          intersectionObserver.disconnect()
        }
        intersectionObserver = uni.createIntersectionObserver(this, { observeAll: true });
        intersectionObserver.relativeTo('.uni-im-list .scroll-view', { top: -50, bottom: -50 })
          .observe('.uni-im-msg', (res) => {
            const msgId = res.id
            const msgRef = this.$refs['uni-im-msg'].find(item => item.msg._id == msgId)
            if (!msgRef) {
              // console.log('找不到msgRef', msgId);
              return
            }
            const isAppear = appearObj[msgId] || false;
            if (res.intersectionRatio > 0 && !isAppear) {
              appearObj[msgId] = true;
              msgRef.onAppear()
              this.msgOnAppear(msgId)
              // console.error('出现了',msgRef.msg.body)

              // 调用扩展点，通知消息列表某条消息进入显示区。
              uniIm.extensions.invokeExts('msg-appear', msgRef.msg, {
                user_id: uniCloud.getCurrentUserInfo().uid,
                isInternalUser: this.uniIDHasRole('staff'),
              })
            } else if (!res.intersectionRatio > 0 && isAppear) {
              appearObj[msgId] = false;
              msgRef.onDisappear()
              // console.error('消失了',msgRef.msg.body)

              // 调用扩展点，通知消息列表某条消息离开显示区。
              uniIm.extensions.invokeExts('msg-disappear', msgRef.msg, {
                user_id: uniCloud.getCurrentUserInfo().uid,
                isInternalUser: this.uniIDHasRole('staff'),
              })
            }
          })
      },
      viewMsg(msgList) {
        this.$refs['view-msg'].open(msgList);
      },
      async onScroll(e) {
        this.scrollTracker.scroll(e.detail.scrollTop)
        this.onScroll.end = false

        if (this.onScroll.timeoutId) {
          clearTimeout(this.onScroll.timeoutId)
        }
        this.onScroll.timeoutId = setTimeout(() => {
          this.onScroll.end = true
        }, 500)

        currentScrollTop = e.detail.scrollTop
        // #ifndef APP-NVUE
        // 移动端 safari内核浏览器有滚动锚定问题，需要通过点击点击“加载按钮”加载历史消息
        if (!this.canAnchor && this.isMpPlatform) {
          return
        }
        // #endif

        await this.beforeLoadMore()
      },
      async onScrollToLower() {
        this.scrollTracker.reachBottom()
        this.hasNewMsg = false
      },
      async canHoldScrollDo(fn){
        return new Promise((resolve) => {
          // #ifdef APP-NVUE
          fn()
          resolve()
          // #endif
          
          // #ifndef APP-NVUE
          let tryIndex = 0
          const tryInsert = () => {
            tryIndex++
            if (tryIndex > 100) {
              console.error("防止未意料的情况下“死循环”限制最多尝试100次");
              // 防止死循环，最多尝试100次
              fn()
              return resolve()
            }
            const msgListIsNoFull = this.visibleMsgList.length < 10
            if (msgListIsNoFull || currentScrollTop > 9 || !this.canAnchor) {
              if (!this.canAnchor) {
                if (this.visibleMsgList.length && this.onScroll.end === false) {
                  return setTimeout(() => {
                    tryInsert()
                  }, 500)
                }
                fn()
                this.scrollTop = currentScrollTop
                this.$nextTick(async () => {
                  let itemScrollTop = await this.getItemScrollTopByIndex(pageLimit - 1)
                  // console.log('itemScrollTop',itemScrollTop);
                  if (this.isWidescreen) {
                    itemScrollTop -= 110
                  } else {
                    itemScrollTop -= 30
                  }
                  this.scrollTop = itemScrollTop
                  currentScrollTop = itemScrollTop
                })
              } else {
                fn()
              }
              resolve()
            } else {
              // console.error('不能插入',currentScrollTop);
              this.scrollTop = currentScrollTop
              this.$nextTick(async () => {
                this.scrollTop = 10
                currentScrollTop = 10
                tryInsert()
              })
            }
          }
          tryInsert()
          // #endif
        })
      },
      /**
       * @description 防止非app-nvue端，部分情况下在顶部插入消息时滚动锚定生效的问题
       */
      async insertMsg(data) {
        // 重新获取会话对象，防止web pc端 切换太快引起的会话对象指向错误
        const conversation = await uniIm.conversation.get(data[0].conversation_id)
        this.canHoldScrollDo(()=>{
          conversation.msgList.unshift(...data)
          // 有新消息插入laterRenderIndex的值重新设置
          this.laterRenderIndex += data.length
        })
      },
      equalPrevTime(index) {
        if (index === 0) {
          return false
        } else if (index == this.visibleMsgList.length - 1) {
          return false
        } else {
          const getFriendlyTime = (msg) => {
            return uniIm.utils.toFriendlyTime(msg.create_time || msg.client_create_time)
          }
          return getFriendlyTime(this.visibleMsgList[index]) == getFriendlyTime(this.visibleMsgList[index - 1])
        }
      },
      async showCallMe() {
        let msgId = this.conversation.call_list.pop()
        console.log('msgId', msgId)
        this.showMsgById(msgId)
      },
      showLast(duration = 300) {
        let mLength = this.visibleMsgList.length
        this.showMsgByIndex(mLength - 1, duration)
        this.hasNewMsg = false
        this.scrollTracker.reachBottom()
      },
      notifyNewMsg() {
        this.hasNewMsg = true
        if (this.scrollTracker.isAtBottom()) {
          this.showLast()
        }
      },
      // #ifndef APP-NVUE
      async getItemScrollTopByIndex(index) {
        return await new Promise((resolve, rejece) => {
          const query = uni.createSelectorQuery().in(this);
          query.select('#item-' + index).boundingClientRect(data => {
            if (!data) {
              return console.log('找不到 showMsgByIndex #item-' + index);
            }
            let val = currentScrollTop + data.top + data.height
            if (val < 0) {
              val = 0
            }
            resolve(val)
          }).exec()
        })
      },
      // #endif
      async showMsgByIndex(index, duration = 300) {
        if (index == -1) {
          return
        }
        // #ifdef APP-NVUE
        let target = this.$refs['item-' + index][0];
        // console.log('滚动到第', index, target);
        const nativePluginDom = uni.requireNativePlugin('dom')
        nativePluginDom.scrollToElement(target, {
          // animated: duration != 0,
          // offset: 999
        });
        // #endif

        // #ifndef APP-NVUE
        let listHeight = this.systemInfo.windowHeight
        // #ifdef H5
        if (uniIm.isWidescreen) {
          listHeight = document.querySelector('.uni-im-list uni-scroll-view').clientHeight
          // console.log('listHeight',listHeight)
        } else {
          listHeight -= 44
        }
        // #endif

        const itemScrollTop = await this.getItemScrollTopByIndex(index)
        const val = itemScrollTop - listHeight * 0.7 + parseInt(this.paddingBottom)

        // 赋值为当前滚动条的高度
        this.scrollTop = currentScrollTop
        // 设置一个新值触发视图更新 -> 滚动
        this.$nextTick(async () => {
          this.scrollTop = val
          currentScrollTop = val
        })
        // #endif
      },
      // 测试专用
      async sliderChange(e) {
        let index = e.detail.value
        console.log(index)
        this.val = index
        this.showMsgByIndex(index)
      },
      async showMsgById(msgId) {
        // 找到消息的索引
        let index = this.visibleMsgList.findIndex(i => i._id == msgId)
        if (index === -1) {
          // 如果找不到，先加载更多，再找
          uni.showLoading();
          const {_findIndex} = this.showMsgById
          if (!_findIndex) {
            this.showMsgById._findIndex = 0
          } else if (_findIndex > 30) {
            uni.hideLoading()
            this.showMsgById._findIndex = false
            return console.error('防止特殊情况下死循环，不加载30屏以外的引用数据');
          }
          this.showMsgById._findIndex++
          await this.loadMore()
          this.showMsgById._findIndex = false
          await uniIm.utils.sleep(300)
          uni.hideLoading()
          return await this.showMsgById(msgId)
        }
        
        this.activeMsgId = msgId
        setTimeout(() => {
          this.activeMsgId = ''
        }, 2000);
        this.showMsgByIndex(index)

        // 如果是显示群公告，则设置未读的群公告内容为 false
        if (this.visibleMsgList[index].action === "update-group-info-notification") {
          this.conversation.unreadGroupNotification = false
        }

      },
      closeGroupNotification(e) {
        if (e.show === false) {
          this.conversation.unreadGroupNotification = false
        }
      },
      isChecked(msg) {
        return this.checkedMsgList.some(i => i._id === msg._id)
      },
      checkMsg(msg) {
        if (!this.chooseMore) {
          return
        }
        let checkedMsgList = this.checkedMsgList
        if (this.isChecked(msg)) {
          checkedMsgList.splice(checkedMsgList.findIndex(i => i._id === msg._id), 1)
        } else {
          checkedMsgList.push(msg)
        }
        this.$emit('update:checkedMsgList', checkedMsgList)
      },
      //当前用户自己的uid
      current_uid() {
        return uniCloud.getCurrentUserInfo().uid;
      },
      showControl(e) {
        this.$emit('showControl', e)
      },
      chatInputContentAddcallUser(e) {
        this.$emit('chatInputContentAddcallUser', e)
      },
      retriesSendMsg(e) {
        this.$emit('retriesSendMsg', e)
      },
      clickItem() {
        this.$emit('clickItem')
      },
      putChatInputContent(msgBody) {
        this.$emit('putChatInputContent', msgBody)
      }
    }
  }

</script>

<style lang="scss" scoped>
  .root,
  .uni-im-list {
    flex: 1;
    background-color: transparent;
  }

  .item {
	  margin:10px 0;
    // border: solid 1px #0055ff;
  }

  .mg-15 {
    margin: 15px;
  }

  .data-state-tip-box {
    // height: 35px;
    align-items: center;
    justify-content: center;
    flex-direction: row;
    color: #999999;
    margin-bottom: -5px;
    margin-top: 10px;
  }

  .data-state-tip-text {
    height: 36px;
    line-height: 36px;
    font-size: 12px;
    margin: 0 5px;
    color: #999999;
  }

  /* #ifndef APP-NVUE */
  .data-state-tip-icon {
    justify-content: center;
    align-items: center;
    display: flex;
    animation: rotation 1s linear infinite;
  }

  @keyframes rotation {
    0% {
      transform: rotate(0deg);
    }

    100% {
      transform: rotate(360deg);
    }
  }

  /* #endif */

  /* #ifdef H5 */
  .loadMore-btn {
    font-size: 14px;
    color: #666;
  }

  .loadMore-btn::after {
    display: none;
  }

  /* #endif */
  .msg-box {
    position: relative;
    transition-property: background-color;
    transition-duration: 2s;
    flex-direction: row;
  }

  .msg-box .checkbox {
    margin: 30px 0 0 10px;
    // transform: translateX(20px);
  }

  .msg-box .mask {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 999;
    width: 100%;
    height: 100%;
  }

  .active-msg {
    background-color: #f9f9f9;
  }

  .slider-box {
    border: 1px solid #000;
    position: fixed;
    width: 750rpx;
    height: 55px;
    top: 60px;
    right: 0;
    z-index: 999;
    background-color: #FFF;
  }

  .showCallMe {
    background-color: #62caf8;
    border-radius: 50px;
    padding: 2px 15px;
    font-size: 12px;
    color: #FFF;
    position: fixed;
    right: 5px;
    top: 10px;
    /* #ifdef H5 */
    top: 55px;
    cursor: pointer;

    @media screen and (min-device-width:960px) {
      top: calc(7vh + 20px);
      right: 30px;
      font-size: 16px;
    }

    /* #endif */
  }

  .group-notification-popup {
    z-index: 9999;
  }

  .new-msg-bar {
    position: absolute;
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    right: 40px;
    bottom: 10px;
    font-size: 12px;
    background-color: white;
    color: #007fff;
    padding: 5px 8px 5px 5px;
    border-radius: 15px 15px 15px 15px;
    /* #ifdef H5 */
    pointer-events: auto;
    cursor: pointer;
    /* #endif */
  }
</style>